﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace Blade {
	public static class Services {
		static readonly MethodInfo _containsService = typeof(IServiceProvider)
			.GetMethod("ContainsService", BindingFlags.Public | BindingFlags.Instance);
		static readonly MethodInfo _tryGetService = typeof(IServiceProvider)
			.GetMethod("TryGetService", BindingFlags.Public | BindingFlags.Instance);

		static readonly object[] EmptyArgs = new object[0];

		public static bool ContainsService(this IServiceProvider provider, Type type) {
			return (bool)_containsService
				.MakeGenericMethod(type)
				.Invoke(provider, EmptyArgs);
		}
		public static bool TryGetService(this IServiceProvider provider, Type type, out object value) {
			var args = new object[1];
			var result = _tryGetService
				.MakeGenericMethod(type)
				.Invoke(provider, args);
			value = args[0];
			return (bool)result;
		}
		public static T GetService<T>(this IServiceProvider provider) {
			T value;
			if (!provider.TryGetService(out value))
				return default(T);
			return value;
		}
		public static object GetService(this IServiceProvider provider, Type type) {
			object value;
			if (!provider.TryGetService(type, out value))
				return null;
			return value;
		}

		public static void AddService<T>(this IServiceContainer container, T instance) {
			container.AddService<T>(instance, ServiceType.Current);
		}
		public static void AddService<T>(this IServiceContainer container, T instance, ServiceType kind) {
			container.AddService<T>((c) => instance, kind);
		}
		public static void AddService<T>(this IServiceContainer container, Func<IServiceContainer, T> callback) {
			container.AddService<T>(callback, ServiceType.Current);
		}

		public static TService Inject<TService, TImplementor>(this IServiceContainer container)
			where TImplementor : TService {
			var constructors = typeof(TImplementor).GetConstructors(BindingFlags.Public | BindingFlags.Instance);
			var canidates = from ctor in constructors
							let parameters = ctor.GetParameters()
							where parameters.All((p) => !p.IsOut)
							where parameters.All((p) => container.ContainsService(p.ParameterType))
							orderby parameters.Length
							select ctor;
			foreach (var ctor in canidates) {
				try {
					var arguments = (from p in ctor.GetParameters()
									 select container.GetService(p.ParameterType)
									).ToArray();
					return (TService)ctor.Invoke(arguments);
				} catch { }
			}
			throw new NotSupportedException();
		}
	}
}
